package tool

import (
	"encoding/json"
	"errors"
	"github.com/FZambia/sentinel"
	"github.com/gomodule/redigo/redis"
	"strconv"
	"time"
)

var redisConn *redis.Pool

type RedisConf struct {
	Host         string        `yaml:"host" comment:"哨兵模式无需传"`
	DB           int           `yaml:"db" comment:"redis database"`
	Password     string        `yaml:"password"`
	MaxIdle      int           `yaml:"maxIdle"`
	MaxActive    int           `yaml:"maxActive"`
	IdleTimeout  time.Duration `yaml:"idleTimeout"`
	MasterName   string        `yaml:"masterName" comment:"哨兵模式默认mymaster"`
	SentinelAddr []string      `yaml:"sentinelAddr" comment:"哨兵模式必传 哨兵ip:port"`
}

var RedisDB redisDB

type redisDB int

// Setup Initialize the Redis instance
func EnableRedis(conf RedisConf) error {
	redisConn = &redis.Pool{
		MaxIdle:     conf.MaxIdle,
		MaxActive:   conf.MaxActive,
		IdleTimeout: conf.IdleTimeout,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", conf.Host, redis.DialDatabase(conf.DB))
			if err != nil {
				return nil, err
			}
			if conf.Password != "" {
				if _, err := c.Do("AUTH", conf.Password); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}
	return nil
}
func EnableSentinelRedis(conf RedisConf) error {
	if conf.MasterName == "" {
		conf.MasterName = "mymaster"
	}
	sntnl := &sentinel.Sentinel{
		Addrs:      conf.SentinelAddr,
		MasterName: conf.MasterName,
		Dial: func(addr string) (redis.Conn, error) {
			timeout := 500 * time.Millisecond
			c, err := redis.Dial("tcp", addr, redis.DialConnectTimeout(timeout),
				redis.DialReadTimeout(timeout),
				redis.DialWriteTimeout(timeout))
			if err != nil {
				return nil, err
			}
			return c, nil
		},
	}
	redisConn = &redis.Pool{
		MaxIdle:     conf.MaxIdle,
		MaxActive:   conf.MaxActive,
		IdleTimeout: conf.IdleTimeout,
		Wait:        true,
		Dial: func() (redis.Conn, error) {
			masterAddr, err := sntnl.MasterAddr()
			if err != nil {
				return nil, err
			}
			c, err := redis.Dial("tcp", masterAddr)
			if err != nil {
				return nil, err
			}
			if conf.Password != "" {
				if _, err := c.Do("AUTH", conf.Password); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if !sentinel.TestRole(c, "master") {
				return errors.New("Role check failed")
			} else {
				return nil
			}
		},
	}
	return nil
}

// 设置一个slice或者struct
func (r redisDB) Set(key string, data interface{}, time int) error {
	conn := redisConn.Get()
	defer conn.Close()

	value, err := json.Marshal(data)
	if err != nil {
		return err
	}

	_, err = conn.Do("SET", key, value)
	if err != nil {
		return err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return err
		}

	}

	return nil
}

// 获取一个slice或者struct返回的bytes
func (r redisDB) Get(key string) ([]byte, error) {
	conn := redisConn.Get()
	defer conn.Close()

	reply, err := redis.Bytes(conn.Do("GET", key))
	if err != nil {
		return nil, err
	}

	return reply, nil
}

// 通过此方法可直接反序列化对象  设置可通过Set方法即可
func (r redisDB) GetStructOrSlice(key string, value interface{}) error {
	conn := redisConn.Get()
	defer conn.Close()

	reply, err := redis.Bytes(conn.Do("GET", key))
	if err != nil {
		return err
	}
	err = json.Unmarshal(reply, value)
	if err != nil {
		return err
	}
	return nil
}
func (r redisDB) SetString(key string, value string, time int) error {
	conn := redisConn.Get()
	defer conn.Close()
	_, err := conn.Do("SET", key, value)
	if err != nil {
		return err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return err
		}

	}
	return nil
}
func (r redisDB) GetString(key string) (string, error) {
	conn := redisConn.Get()
	defer conn.Close()
	return redis.String(conn.Do("GET", key))
}
func (r redisDB) SetBool(key string, value bool, time int) error {
	conn := redisConn.Get()
	defer conn.Close()
	_, err := conn.Do("SET", key, value)
	if err != nil {
		return err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return err
		}

	}
	return nil
}
func (r redisDB) GetBool(key string) (bool, error) {
	conn := redisConn.Get()
	defer conn.Close()
	return redis.Bool(conn.Do("GET", key))
}

func (r redisDB) GetInt(key string) (int, error) {
	conn := redisConn.Get()
	defer conn.Close()
	return redis.Int(conn.Do("GET", key))
}
func (r redisDB) SetInt(key string, data int, time int) error {
	conn := redisConn.Get()
	defer conn.Close()
	_, err := conn.Do("SET", key, data)
	if err != nil {
		return err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return err
		}
	}
	return nil
}

// 分布式锁所需 value请设置每个客户端唯一值
/*
如果 SETNX 返回1，说明该进程获得锁，
如果 SETNX 返回0，说明其他进程已经获得了锁，进程不能进入临界区。进程可以在一个循环中不断地尝试 SETNX 操作，以获得锁。
*/
func (r redisDB) SetNX(key string, value string, time int) (int, error) {
	conn := redisConn.Get()
	defer conn.Close()
	var nx int
	nx, err := redis.Int(conn.Do("SETNX", key, value))
	if err != nil {
		return 0, err
	}
	if time != 0 {
		_, err = conn.Do("EXPIRE", key, time)
		if err != nil {
			return 0, err
		}
	}
	return nx, nil
}

// Exists check a key
func (r redisDB) Exists(key string) bool {
	conn := redisConn.Get()
	defer conn.Close()

	exists, err := redis.Bool(conn.Do("EXISTS", key))
	if err != nil {
		return false
	}

	return exists
}

func (r redisDB) Incre(key string) (int, error) {
	conn := redisConn.Get()
	defer conn.Close()
	value, err := redis.Int(conn.Do("INCR", key))
	if err != nil {
		return 0, err
	}
	return value, nil
}

// Delete delete a kye
func (r redisDB) Delete(key string) (bool, error) {
	conn := redisConn.Get()
	defer conn.Close()

	return redis.Bool(conn.Do("DEL", key))
}

// LikeDeletes batch delete
func (r redisDB) LikeDeletes(key string) error {
	conn := redisConn.Get()
	defer conn.Close()

	keys, err := redis.Strings(conn.Do("KEYS", "*"+key+"*"))
	if err != nil {
		return err
	}

	for _, key := range keys {
		_, err = r.Delete(key)
		if err != nil {
			return err
		}
	}

	return nil
}
func (r redisDB) ZAdd(key string, value, score int) error {
	conn := redisConn.Get()
	defer conn.Close()
	_, err := conn.Do("ZADD", key, score, value)
	return err
}

func (r redisDB) ZRank(key string, value int) (int, error) {
	conn := redisConn.Get()
	defer conn.Close()

	rank, err := redis.Int(conn.Do("ZRANK", key, strconv.Itoa(value)))
	if err != nil {
		return 0, err
	}
	return rank, nil
}

func (r redisDB) ZRange(key string, offset int) ([]int, error) {
	conn := redisConn.Get()
	defer conn.Close()
	ranks, err := redis.Ints(conn.Do("ZRANGE", key, 0, offset))
	if err != nil {
		return nil, err
	}

	return ranks, nil
}

// 增加计数
func (r redisDB) INCRBY(key string, increment int) (int, error) {
	conn := redisConn.Get()
	defer conn.Close()
	newNum, err := redis.Int(conn.Do("INCRBY", key, increment))
	if err != nil {
		return 0, err
	}
	return newNum, nil
}

// 获取计数
func (r redisDB) GetCounter(key string) (int, error) {
	conn := redisConn.Get()
	defer conn.Close()
	count, err := redis.Int(conn.Do("GET", key))
	if err != nil {
		return 0, err
	}
	return count, nil
}

// 初始化计数器
func (r redisDB) InitCounter(key string) error {
	conn := redisConn.Get()
	defer conn.Close()
	_, err := conn.Do("SET", key, 0)
	if err != nil {
		return err
	}
	return nil
}

var ErrCounterHasBeenZero = errors.New("the counter has been 0")

// 减少计数
func (r redisDB) DECRBY(key string, decrement int) (int, error) {
	conn := redisConn.Get()
	defer conn.Close()
	count, err := r.GetCounter(key)
	if err != nil {
		return 0, err
	}
	if count == 0 {
		return 0, ErrCounterHasBeenZero
	}
	newNum, err := redis.Int(conn.Do("DECRBY", key, decrement))
	if err != nil {
		return 0, err
	}
	return newNum, nil
}

// 根据pattern获取keys(可能成为性能瓶颈)
func (r redisDB) Keys(pattern string) ([]string, error) {
	conn := redisConn.Get()
	defer conn.Close()
	keys, err := redis.Strings(conn.Do("KEYS", pattern))
	if err != nil {
		return keys, err
	}
	return keys, nil
}

// lua脚本执行
func (r redisDB) LuaScript(keyCount int, keysAndArgs []interface{}, lua string) (interface{}, error) {
	conn := redisConn.Get()
	defer conn.Close()
	script := redis.NewScript(keyCount, lua)
	return script.Do(conn, keysAndArgs...)
}

// 命令操作
func (r redisDB) Operator(command string, value ...interface{}) (interface{}, error) {
	conn := redisConn.Get()
	defer conn.Close()
	return conn.Do(command, value...)
}
